package org.luxor.html.config;

import org.luxor.html.config.jcryption.JCryption;
import org.luxor.html.config.jcryption.aes.AesCtr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.KeyPair;
import java.text.DateFormat;
import java.util.Date;

/**
 * jCryption 表单参数非对称加解密功能
 *
 * @author Gabriel Andery @version 1.0
 */
@Configuration
public class JCryptionAutoConfig {

    @Bean
    public ServletRegistrationBean<CryptoServlet> JcryptionServletRegistrationBean() {
        ServletRegistrationBean<CryptoServlet> bean = new ServletRegistrationBean<>(new CryptoServlet());
        bean.addUrlMappings("/encrypt");
        return bean;
    }

    /**
     * Servlet example for jCryption
     *
     * @author Gabriel Andery
     * @version 1.0
     */
    public static class CryptoServlet extends HttpServlet {
        private static final long serialVersionUID = 4510110365995157499L;
        private final Logger logger = LoggerFactory.getLogger(CryptoServlet.class);

        private boolean isTrue(String param) {
            return param != null && param.equals("true");
        }

        @Override
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException {

            /** Generates a KeyPair for RSA **/
            if (isTrue(request.getParameter("generateKeyPair"))) {
                try {
                    JCryption jc = new JCryption();
                    KeyPair keys = jc.getKeyPair();
                    request.getSession().getServletContext().setAttribute("jCryptionKeys", keys);

                    String e = jc.getPublicExponent();
                    String n = jc.getKeyModulus();
                    String md = String.valueOf(jc.getMaxDigits());

                    response.getWriter().print("{\"e\":\"" + e + "\",\"n\":\"" + n + "\",\"maxdigits\":\"" + md + "\"}");
                    return;
                } catch (Exception e) {
                    logger.error("Generates a KeyPair for RSA, failure", e);
                    throw e;
                }
            }
            /** jCryption handshake **/
            else if (isTrue(request.getParameter("handshake"))) {
                try {
                    /** Decrypts password using private key **/
                    KeyPair keyPair = (KeyPair) request.getSession().getServletContext().getAttribute("jCryptionKeys");
                    JCryption jc = new JCryption(keyPair);
                    String key = jc.decrypt(request.getParameter("key"));

                    request.getSession().getServletContext().removeAttribute("jCryptionKeys");
                    request.getSession().getServletContext().setAttribute("jCryptionKey", key);

                    /** Encrypts password using AES **/
                    String ct = AesCtr.encrypt(key, key, 256);

                    response.getWriter().print("{\"challenge\":\"" + ct + "\"}");
                    return;
                } catch (Exception e) {
                    logger.error("jCryption handshake, failure", e);
                    throw e;
                }
            }
            /** jCryption request to decrypt a String **/
            else if (isTrue(request.getParameter("decryptData")) && request.getParameter("jCryption") != null) {
                try {
                    /** Decrypts the request using password **/
                    String key = (String) request.getSession().getServletContext().getAttribute("jCryptionKey");
                    String pt = AesCtr.decrypt(request.getParameter("jCryption"), key, 256);

                    response.getWriter().print("{\"data\":\"" + pt + "\"}");
                    return;
                } catch (Exception e) {
                    logger.error("jCryption request to decrypt a String, failure", e);
                    throw e;
                }
            }
            /** jCryption request to encrypt a String **/
            else if (isTrue(request.getParameter("encryptData")) && request.getParameter("jCryption") != null) {
                try {
                    /** Encrypts the request using password **/
                    String key = (String) request.getSession().getServletContext().getAttribute("jCryptionKey");
                    String ct = AesCtr.encrypt(request.getParameter("jCryption"), key, 256);

                    response.getWriter().print("{\"data\":\"" + ct + "\"}");
                    return;
                } catch (Exception e) {
                    logger.error("jCryption request to encrypt a String, failure", e);
                    throw e;
                }
            }
            /** A test request from jCryption **/
            else if (isTrue(request.getParameter("decryptTest"))) {
                try {
                    /** Encrypts a timestamp **/
                    String key = (String) request.getSession().getServletContext().getAttribute("jCryptionKey");
                    String date = DateFormat.getInstance().format(new Date());
                    String ct = AesCtr.encrypt(date, key, 256);

                    response.getWriter().print("{\"encrypted\":\"" + ct + "\", \"unencrypted\":\"" + date + "\"}");
                    return;
                } catch (Exception e) {
                    logger.error("A test request from jCryption, failure", e);
                    throw e;
                }
            }
        }

        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse res)
                throws IOException, ServletException {
            this.doPost(req, res);
        }
    }
}
